Skip to content

docs(skills): a suite composes under manifest preserve; a flat composition of two apps under one package is refused - #33475

Merged
os-zhuang merged 1 commit into
mainfrom
claude/project-thread-rqofat
Nov 15, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/project-thread-rqofat

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #32977

domain:skills#1 · os-project-manager · session_01FEkQ5mo8ZsDz2BHVZiwWLs

Clause-②: no
Metadata-protocol: none

维护者速读

What changes

Paid in the same file (no ceiling moved)

  • bootstrap.md: the title's "(moved verbatim from SKILL.md)" (no longer true once this section changes); "this is how real multi-app composition happens" and "equal the physical table name" (the second restates objectstack-platform/SKILL.md § Object naming); "Use barrel exports to keep config clean:" and "A minimal but complete project from scratch:" (each restates its heading); "(traditional)" and "(key becomes name) ... for readability" in the Map Format comments (the sentence above the block says the key becomes the name); the stack.zod.ts file pointer (references/_index.md carries the schema pointers).
  • objectstack-ui/SKILL.md: the subforms example's "relationshipField + columns are derived from the child object" comment (the paragraph under the list says the FK and columns are derived in every case); the requiresObject / requiresService note shortened to what it says ("hide an item whose optional system object or kernel service is missing"). The pinned lines named in the file's CEILINGS entry are untouched.

Why

Read on origin/main 34c0522, packages/spec/src/stack.zod.ts:

  • SINGLE_APP_PACKAGE_TYPES = app, module. SINGLE_APP_COMPOSE_REMEDY is the text every single-app finding names: "compose the packages into one artifact with composeStacks(stacks, { manifest: 'preserve' }), which keeps each package's own id (a flat composition puts every app back under one)".
  • composeStacks calls collectComposedSingleAppErrors(stacks, composed, opts.manifest) and throws StackSingleAppViolationError (code = 'STACK_SINGLE_APP_VIOLATION', status = 422 from StackRefusalError) with the header composeStacks single-app validation failed (N issues):. With no packages[] in the result, flatSingleAppFinding refuses when the composed manifest's type is in the set and the composed apps has more than one entry, naming each app with the input it came from.
  • examples/app-multi-package/objectstack.config.ts line 68: export default composeStacks([ordersStack, coreStack], { manifest: 'preserve' });, so the old "No app in this repo uses it" was false.

Acceptance notes

  • resolveArtifactCollections (@objectstack/runtime, named in 6846119916 as the platform reader the remedy could name) is not taught: the ask is the preserve remedy, the file has no headroom, and an author composes with composeStacks, not with the runtime reader.
  • No eval is added: the card asks for teaching text only.
  • origin/main moved to b5f2f51 (docs(skills): a vetted overlap-check hook template for insert and update #33292) after the branch was cut; it touches none of these files, so no merge.

Verification

Head f4fc5ea9, base origin/main 34c0522.

Line and token budget (skills/**)
  • skills/objectstack-platform/references/bootstrap.md: 190 to 188 lines, 5398 to 5390 bytes; token ratchet 1342 to 1340 (ceiling 1343, headroom 3).
  • skills/objectstack-ui/SKILL.md: 301 to 301 lines, 15276 to 15253 bytes; token ratchet 3814 to 3809 (ceiling 3814, headroom 5).
  • Whole package, all skills/*/SKILL.md: 4602 to 4602 lines. Bundle total (whole shipped tree): 244649 to 244642 tokens.
  • node scripts/pm/check-skill-line-ratchet.mjs: exit 0. node scripts/check-skills-token-ratchet.mjs: exit 0. No ceiling raised.
Derived gates: 48 derived, 46 run green, 2 NOT MEASURED

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 48 families for the three paths. turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --filter=@objectstack/spec ran first under the verify lock (VERDICT command-exit 0, 87s). Each family was then run with its exit code captured before any pipe.

  • 46 exit 0, among them check-adr-0087-registration, check-empty-changeset, check-skills-token-ratchet (scan and --self-test each), @objectstack/lint check:doc-formula-expressions, @objectstack/spec check:skill-docs and check:skill-refs, check:doc-authoring, check:nul-bytes, check:pm-governed-merges, check:published-files, check:scaffold-emission-policy, check:skill-compatibility, check:skill-frame-sync, check:skill-identifier-liveness, check:skill-links; check:dts-closure and check:sourcemap-no-sources-content over the 5 built packages only.
  • NOT MEASURED, exit 3 PREREQUISITE NOT MET (every package's dist/): check:dual-build-cjs-loads, check:lean-entry-closure. No package source is touched, so no full build was paid; CI builds and runs them.
  • --ran: "48 derived famil(ies) accounted for — 46 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)".
  • CI families are read from this PR's check-runs, not re-run here.
Tier and governed readings
  • dispatch-gates --tier: MANDATORY contract-review tier, skills/** under clause ① (the published catalog); no one-line exit for this surface.
  • node scripts/pm/check-governed-merges.mjs --branch claude/project-thread-rqofat: exit 3, GOVERNED, landing tier H. 2 of 3 paths on the register (skills/**); the changeset is not. Size +32 / -24, under the 3000 threshold.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5mo8ZsDz2BHVZiwWLs

…ition of two apps is refused

bootstrap.md's Multi-App Composition section said no app in this repo
uses composeStacks and named no strategy for a suite. It now teaches
composeStacks(stacks, { manifest: 'preserve' }) for a suite, points at
examples/app-multi-package, and states the STACK_SINGLE_APP_VIOLATION
(422) refusal of a flat composition. objectstack-ui SKILL.md App
Navigation names the same remedy. Both files pay in-file by deleting
restatements.

Claude-Session: https://claude.ai/code/session_01FEkQ5mo8ZsDz2BHVZiwWLs
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f4fc5ea9ef745844ea53e28ac399fa53f8f62f17
Local-runs: none
Scope: skills/objectstack-platform/references/bootstrap.md, skills/objectstack-ui/SKILL.md + none

Read on origin/main (the PR's base 34c05229; main has since moved to b5f2f516, which touches none of these files and not packages/spec/src/stack.zod.ts): composeStacks, composeBuiltStacks, ComposeStacksOptionsSchema.manifest, SINGLE_APP_PACKAGE_TYPES, SINGLE_APP_COMPOSE_REMEDY, singleAppFinding, validateSingleApp, collectComposedSingleAppErrors, flatSingleAppFinding, residualSingleAppFinding, StackRefusalError, StackSingleAppViolationError; examples/app-multi-package/objectstack.config.ts and its two packages; docs/adr/0019-app-as-consumer-unit.md; card #32977 with every comment (fold 6846119916, dev report 6884932864); card #33135 with its comment.

① Derived judgments

  • Accept set: none moves. The diff is skill prose and a changeset; no schema, no runtime, no gate script. What the prose teaches is checked against the accept set that is already on main (④).
  • Public surface: the @objectstack/skills published text only (two files). Right.
  • Gates: the head's check-runs are the verdicts. At review 18 completed success, 18 skipped, none failed, and 9 still in progress (Lint · docs & code guards, Lint · repo & tooling gates, Lint · CI, release & engine gates, Lint · ESLint, Check Changeset, Type Check · source gates, Type Check · consumer gates, Test Core (1/8), Test Core (2/8)). The token and line ratchets and the skill-doc checks ride the lint lanes still running; the merge waits on them green. This record does not stand in for them.

② Semver level

  • .changeset/32977-skills-suite-composes-under-preserve.md: '@objectstack/skills': patch. Matches the repo's convention for skill-text corrections (the last five docs(skills) merges on main carry the same line). Right.
  • Clause-②: no on the PR body and the changeset: no published key is added, removed or narrowed. Right. Metadata-protocol: none: right.

③ Boundary flags

④ Design judgment

skills/objectstack-platform/references/bootstrap.md, § Multi-App Composition, against the card's Work items 1 and 2 and stack.zod.ts on main:

  • "A suite (an app package plus module packages, one app each) composes with composeStacks(stacks, { manifest: 'preserve' }), which keeps each package's own id": holds. SINGLE_APP_COMPOSE_REMEDY says the same words; under 'preserve' each input with a manifest is assembled into a body of its own and registered under its own id, so a one-app-per-package suite leaves no residual app (collectComposedSingleAppErrors doc). The qualifier "one app each" with each being a package is what keeps the residual rule from biting, so the sentence does not over-promise.
  • "(examples/app-multi-package)": holds. Line 68 of the config is composeStacks([ordersStack, coreStack], { manifest: 'preserve' }), and the two inputs are a type: 'app' package (com.example.multi.core) and a type: 'module' package (com.example.multi.orders). The old "No app in this repo uses it" is gone. Work item 2 done.
  • "A flat manifest ('last', the default, 'first' or an index) keeps one": holds. The schema is z.union([z.enum(['first', 'last', 'preserve']), z.number().int().min(0)]).default('last'); the three non-preserve values are the pick-one set the option doc names.
  • "so two or more apps under one app or module package id are refused": holds on the exact condition. flatSingleAppFinding refuses when the composed manifest's type is in SINGLE_APP_PACKAGE_TYPES (app, module) and the composed apps holds more than one entry; a kept manifest of any other type, or one app in all, passes. The sentence names both the package-type gate and the count.
  • "(STACK_SINGLE_APP_VIOLATION, 422)": holds. StackSingleAppViolationError.code = 'STACK_SINGLE_APP_VIOLATION', status = 422 from StackRefusalError, thrown by composeBuiltStacks on the collectComposedSingleAppErrors result. Work item 1 done.
  • Tried to break it: a single input (stacks.length === 1 returns it untouched) is not a composition and not a suite; a flat composition whose input carries packages[] is judged by the residual rule instead, and is still refused in the case the sentence describes; under 'preserve' an input with no manifest can still be refused (residual app). None of these makes a taught sentence false: the text claims nothing about them.
  • The first paragraph's cuts ("this is how real multi-app composition happens", "equal the physical table name") lose no rule: the table-name rule stands in objectstack-platform/SKILL.md § Object naming ("equals the physical table name"), and "globally unique name" plus "use it directly" keep the instruction.
  • Other cuts ("(moved verbatim from SKILL.md)", "(traditional)", "(key becomes name) ... for readability", "Use barrel exports to keep config clean:", "A minimal but complete project from scratch:", the stack.zod.ts pointer): each restates its heading, the sentence above the block, or references/_index.md (which lists stack.zod.ts). "read the schema before reaching for it" went with the false claim it hedged. No other file in the tree quotes the removed title or sentence. No rule lost.

skills/objectstack-ui/SKILL.md, § App Navigation, against fold 6846119916 row 1:

  • "the suite composes with composeStacks(stacks, { manifest: 'preserve' }), which serves each app under its own package id; a flat composition puts them under one and is refused": holds. It is SINGLE_APP_COMPOSE_REMEDY ("a flat composition puts every app back under one") in the section's words; in a suite the kept manifest is an app or module package, so the unqualified "is refused" is true for the case the paragraph is about. "served under that module's package id" folded into "serves each app under its own package id", and "gone when it is disabled" is kept. Row 1 done.
  • requiresObject / requiresService note shortened: the behaviour (an item hides when the optional object or service is missing) and the prohibition ("never hard-code conditional UI") both stay. No rule lost.
  • Finding 1, nit: the subforms example's comment cut removes this file's only mention of the relationshipField key. The behaviour stays taught (the paragraph under the list: "The relationship FK and grid columns are derived from the child object's metadata in every case"), the old comment taught no FK override either, and the key is taught in objectstack-data/rules/relationships.md and objectstack-ui/rules/pages.md. No effect on correctness; accepted as a deviation.

No must-fix, no should-fix. One nit, accepted. The ADR-0019 relay onto #33135 is the seat's (③), not this diff's.

Implemented-by: claude/project-thread-rqofat
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: PASS

Adopted by the seat 2026-11-14T22:27Z · isolated sub-agent at CONTRACT_REVIEW_TIER · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

维护者速读

席位核对

Verdict: ACCEPT, held for the maintainer · head f4fc5ea9ef745844ea53e28ac399fa53f8f62f17 · card #32977 · report 6884932864 · contract review 6885186638 (PASS on this head, CONTRACT_REVIEW_TIER; Ruling-ref for the in-seat review: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb option A).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f4fc5ea9ef745844ea53e28ac399fa53f8f62f17
Local-runs: none
Scope: skills/objectstack-platform/references/bootstrap.md, skills/objectstack-ui/SKILL.md + none

At-tier re-review of this head. The earlier record 6885186638 is not used as evidence; ③ says which of its findings stand. Read for this record: card #32977 with every comment, the PR body and file list, the net diff against main (merge base 34c05229), the check-runs on the head, and, in the live tree on origin/main 8e63ed87, packages/spec/src/stack.zod.ts (SINGLE_APP_PACKAGE_TYPES, SINGLE_APP_COMPOSE_REMEDY, singleAppFinding, validateSingleApp, collectComposedSingleAppErrors, flatSingleAppFinding, residualSingleAppFinding, ComposeStacksOptionsSchema.manifest, StackRefusalError, StackSingleAppViolationError), examples/app-multi-package/objectstack.config.ts and its two packages, skills/objectstack-platform/SKILL.md, skills/objectstack-platform/references/_index.md, scripts/sync-scaffold-emission-policy.mjs. main moved 98 files past the merge base; none is stack.zod.ts, either hits file, the example, or a ratchet script.

① Derived judgments

  • Accept set: none moves. The diff is two skill prose files and one changeset; no schema, runtime or gate script. Right.
  • Public surface: the published text of @objectstack/skills (two files). Right.
  • Gates: every check-run on the head is completed; none failed, cancelled or timed out. Success includes Lint · docs & code guards, Lint · repo & tooling gates, Lint · CI, release & engine gates, Lint · scoped · tooling self-tests, Check Changeset (3), Governed Surface Queue Guard (2), PR-body guards, Type Check · workspace, Test Core (1/8), Test Core (2/8), Test Core, Build Core. The skipped runs are the UI, docs-build and opt-in lanes this path set does not derive. The token and line ratchets, skill-doc, skill-link and scaffold-policy checks are answered by those lint lanes; nothing was re-run here.

② Semver level

③ Boundary flags

④ Design judgment

skills/objectstack-platform/references/bootstrap.md, § Multi-App Composition, against card Work items 1 to 3 and stack.zod.ts:

  • "A suite (an app package plus module packages, one app each) composes with composeStacks(stacks, { manifest: 'preserve' }), which keeps each package's own id": holds. It is SINGLE_APP_COMPOSE_REMEDY and the manifest option's own doc ("A suite — an app package plus module packages, each with an app of its own — composes under 'preserve'"). Under 'preserve' each input with a manifest is assembled into its own packages[] body, each judged by its own type, id and apps.
  • "A flat manifest ('last', the default, 'first' or an index) keeps one": holds. The schema is the union of 'first', 'last', 'preserve' and a non-negative integer, default 'last'.
  • "so two or more apps under one app or module package id are refused (STACK_SINGLE_APP_VIOLATION, 422)": holds on the exact condition. collectComposedSingleAppErrors gates on the kept manifest's type being in SINGLE_APP_PACKAGE_TYPES (app, module); flatSingleAppFinding refuses when the composed apps has more than one entry; the throw is StackSingleAppViolationError (code STACK_SINGLE_APP_VIOLATION, status 422 from StackRefusalError). Work item 1 done.
  • "No app in this repo uses it" is gone; examples/app-multi-package/objectstack.config.ts ends composeStacks([ordersStack, coreStack], { manifest: 'preserve' }). Work item 2 done. Work item 3: PR feat(spec)!: composeStacks refuses a flat composition that puts two or more apps under one app or module package id, and every packages[] body is held to one app #32898 is on main; the text teaches only what the live tree does.
  • Tried to break it. A flat composition whose kept manifest is neither app nor module, or that carries one app in all, passes: the sentence names both conditions, so it does not over-claim. A flat composition with an input carrying packages[] goes through the residual judge and is still refused in the case described. Under 'preserve' an input with no manifest can leave a residual app and be refused: the text says "package", and a package has a manifest, so no taught sentence is false. A single input is returned untouched and is not a composition.
  • Cuts. "equal the physical table name" stands in objectstack-platform/SKILL.md § Object naming. The stack.zod.ts pointer stands in references/_index.md. The title's "(moved verbatim from SKILL.md)", the two heading restatements and the two Map Format comment glosses carry no rule. sync-scaffold-emission-policy anchors on the ## Complete Working Example heading and the package.json label, both kept. No rule lost.
  • Finding 1, nit: the parenthetical examples/app-multi-package sits in the sentence whose subject is a suite with "one app each", but the example's module (com.example.multi.orders, type: 'module') defines no app; it adds to the core app through navigationContributions. The citation is true for what it is attached to (composing with 'preserve', each package keeping its id), and no taught sentence is false, but a reader opening it for a module that owns an app will not find one. Likewise "one app each" is narrower than the rule (at most one; a module with none composes the same way). No effect on correctness; left as an accepted deviation or a prose follow-up.

skills/objectstack-ui/SKILL.md, § App Navigation, against fold 6846119916 row 1:

  • "the suite composes with composeStacks(stacks, { manifest: 'preserve' }), which serves each app under its own package id; a flat composition puts them under one and is refused": holds. In the suite the paragraph is about, the kept manifest is an app or module package and the apps number two or more, which is exactly the flatSingleAppFinding condition, so the unqualified "is refused" is true there. Row 1 done.
  • "(gone when it is disabled)" moved into a parenthesis after "the objects it shows": still true of the app and of the module's objects alike. No rule lost.
  • requiresObject / requiresService note: the behaviour and the "never hard-code conditional UI" prohibition both stay.
  • Finding 2, nit: the subforms comment cut removes this file's only mention of relationshipField. The paragraph under the list still says the FK and grid columns are derived in every case, and the key is taught in objectstack-data/rules/relationships.md and objectstack-ui/rules/pages.md. No effect on correctness; accepted deviation.

The card carries no eval prompt and asks for none; the scenario judged against is the card's own (an agent composing a suite). No must-fix, no should-fix. Two nits, neither needing a head move.

Implemented-by: claude/project-thread-rqofat
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: PASS

Adopted by the seat 2026-11-15T00:31Z · isolated sub-agent at CONTRACT_REVIEW_TIER (served model verified by the seat) · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Correction: contract-review tier

The contract-review record(s) 6885186638 on this PR say Served-tier: CONTRACT_REVIEW_TIER, and the seat adopted them as "isolated sub-agent at CONTRACT_REVIEW_TIER". That is wrong. The seat launched the reviewing sub-agent without the tier, so it ran at the seat's own default tier, not at CONTRACT_REVIEW_TIER. The seat found this on its own audit. The review was still isolated and adversarial, and its findings stand as an off-tier review. They are not the at-tier record AGENTS.md and the in-seat review ruling require.

The seat re-runs the review at CONTRACT_REVIEW_TIER on this PR's head and posts that record here. Until it does, this PR has no at-tier record.

Merged via the queue into main with commit 539f2d1 Nov 15, 2026
54 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-rqofat branch November 15, 2026 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue size/s tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

3 participants